#include <sobjc-commons/defines.h>
#include <sobjc-commons/str_stream.h>
#include "command.h"
#include "context.h"
#include "method_sig.h"
#include "parse.h"
#include "type.h"

static objc_method_sig_t make_method_sig(const char *, objc_node_t, BOOL);
static objc_method_sig_t validate_method_sigs(const char *,
                                              objc_method_sig_t,
                                              objc_method_sig_t,
                                              BOOL);
static BOOL validate_ctypes(const char *, const char *);
static objc_type_encoding_t make_type_encoding_from_method_types(objc_node_t, int, objc_node_t *, BOOL);
static objc_type_list_t scan_node(objc_node_t, objc_type_t, BOOL);
static void scan_node_type_name(objc_str_stream_t, objc_node_t, BOOL);

static st_table_t global_method_sigs;

static objc_method_sig_t
make_method_sig(const char *name, objc_node_t mth_dclr, BOOL is_global)
{
  objc_method_sig_t sig;
  objc_node_t mth_sel, mtype, *ptypes = NULL;
  objc_class_t cls;
  int i;

  sig = ALLOC(struct objc_method_sig);
  sig->name = name;

  mtype = mth_dclr->a.mth_dclr.mth_spcr->a.mth_spcr.mtype;
  mth_sel = mth_dclr->a.mth_dclr.mth_sel;
  switch (mth_sel->type) {
  case OBJC_MTH_UNARY_SEL_NODE:
    sig->nargs = 0;
    break;
  case OBJC_MTH_KW_SEL_NODE:
    sig->nargs = mth_sel->a.mth_kw_sel.sel->a.l.nsubs;
    sig->vargs = mth_sel->a.mth_kw_sel.vargs;
    ptypes = mth_sel->a.mth_kw_sel.sel->a.l.subs;
    break;
  }

  if (mtype)
    sig->mctype = objc_ctype_from_decl(objc_current_scope, mtype, NULL);
  else {
    sig->mctype = ALLOC_ATOM_N(char, 2);
    if (is_global)
      sprintf(sig->mctype, "%c", OBJC_CTYPE_ID_CODE);
    else
      sprintf(sig->mctype, "%c", OBJC_CTYPE_XSELF_CODE);
  }

  if (sig->nargs) {
    sig->pctypes = ALLOC_N(char *, sig->nargs);
    for (i = 0; i < sig->nargs; i++)
      sig->pctypes[i] = objc_ctype_from_decl(objc_current_scope, ptypes[i], NULL);
  } else
    sig->pctypes = NULL;

  if (mtype)
    sig->mtype_name = objc_generate_type_name(mtype, is_global);
  else {
    if (is_global)
      sig->mtype_name = objc_strdup("id");
    else {
      cls = objc_scope_get_class(objc_current_scope);
      sig->mtype_name = ALLOC_ATOM_N(char, strlen(cls->name)+2);
      sprintf(sig->mtype_name, "%s*", cls->name);
    }
  }

  sig->ptype_names = objc_generate_type_name(mth_sel, is_global);
  sig->imp_type = ALLOC_N(char, strlen(sig->mtype_name) +
                          strlen(sig->ptype_names) + 32);
  sprintf(sig->imp_type, "(%s (*)(id, SEL%s%s%s))", sig->mtype_name,
          sig->nargs ? "," : "", sig->ptype_names,
          sig->vargs ? ", ..." : "");
  sig->enc = make_type_encoding_from_method_types(mtype, sig->nargs,
                                                  ptypes, is_global);

  return sig;
}

objc_method_sig_t
objc_register_global_method_sig(const char *name, objc_node_t mth_dclr)
{
  objc_method_sig_t old = NULL, new;

  new = make_method_sig(name, mth_dclr, YES);
  if (global_method_sigs) {
    if (st_lookup(global_method_sigs, (st_data_t)name, (st_data_t *)&old))
      return validate_method_sigs(name, old, new, YES);
  } else
    global_method_sigs = st_init_strtable();

  st_insert(global_method_sigs, (st_data_t)name, (st_data_t)new);
  return new;
}

objc_method_sig_t
objc_class_register_method_sig(objc_class_t cls,
                               const char *name,
                               objc_node_t dclr)
{
  objc_method_sig_t old = NULL, new;
  objc_class_t temp;

  new = make_method_sig(name, dclr, NO);
  temp = cls;
  while (temp) {
    if (st_lookup(temp->sigs, (st_data_t)name, (st_data_t *)&old)) {
      if (validate_method_sigs(name, old, new, NO))
        return old;
      else
        return NULL;
    } else
      temp = temp->superclass;
  }

  st_insert(cls->sigs, (st_data_t)name, (st_data_t)new);
  return new;
}

static objc_method_sig_t
validate_method_sigs(const char *name,
                     objc_method_sig_t old,
                     objc_method_sig_t new,
                     BOOL is_global)
{
  int i;

  if (old->nargs == new->nargs && old->vargs == new->vargs) {
    if (is_global) {
      if (!STREQ(old->enc->full_code, new->enc->full_code))
        goto error;
    } else {
      if (validate_ctypes(old->mctype, new->mctype)) {
        for (i = 0; i < old->nargs; i++) {
          if (!validate_ctypes(old->pctypes[i], new->pctypes[i]))
            goto error;
        }
      } else
        goto error;
    }
  } else
    goto error;
  return old;

error:
  if (objc_main_opts.verbose) {
    printf("incompatible types error '%s':\n", name);
    printf("  global:       %s\n", is_global ? "YES" : "NO");
    printf("  nargs:        %d : %d\n", old->nargs, new->nargs);
    printf("  vargs:        %s : %s\n",
           old->vargs?"YES":"NO", new->vargs?"YES":"NO");
    if (is_global)
      printf("  objc types:   '%s' : '%s'\n",
             old->enc->full_code, new->enc->full_code);
    else {
      printf("  mtype:        '%s' : '%s'\n", old->mctype, new->mctype);
      for (i = 0; i < old->nargs; i++)
      printf("  ptype[%d]:    '%s' : '%s'\n",
             i, old->pctypes[i], new->pctypes[i]);
    }
  }
  return NULL;
}

static BOOL
validate_ctypes(const char *ctype1, const char *ctype2)
{
  if (STREQ(ctype1, ctype2) ||
      (*ctype1 == OBJC_CTYPE_CLASS_CODE && *ctype2 == OBJC_CTYPE_CLASS_CODE))
    return YES;
  else
    return NO;
}

objc_method_sig_t
objc_get_global_method_sig(const char *name)
{
  if (global_method_sigs)
    return objc_get_method_sig(global_method_sigs, name);
  else
    return NULL;
}

objc_method_sig_t
objc_get_method_sig(objc_method_sig_t sigs, const char *name)
{
  objc_method_sig_t sig = NULL;

  st_lookup(sigs, name, &sig);
  return sig;
}

objc_type_encoding_t
objc_make_type_encoding_from_node(objc_node_t node)
{
  objc_type_t type;

  type = objc_make_type();
  scan_node(node, type, NO);
  return objc_make_type_encoding(objc_make_type_list(type, NULL));
}

static objc_type_encoding_t
make_type_encoding_from_method_types(objc_node_t mnode,
                                     int nargs,
                                     objc_node_t *pnodes,
                                     BOOL is_global)
{
  objc_type_t type;
  objc_type_list_t types;
  int i;

  /* method return */
  type = objc_make_type();
  if (mnode)
    scan_node(mnode, type, is_global);
  else {
    type->code_type = OBJC_TYPE_PRIM_CODE_TYPE;
    type->body.prim = OBJC_TYPE_ID_PRIM_TYPE;
  }
  types = objc_make_type_list(type, NULL);

  /* receiver and selector */
  type = objc_make_type();
  type->code_type = OBJC_TYPE_PRIM_CODE_TYPE;
  type->body.prim = OBJC_TYPE_ID_PRIM_TYPE;
  objc_type_list_add_set(&types, type);
  type = objc_make_type();
  type->code_type = OBJC_TYPE_PRIM_CODE_TYPE;
  type->body.prim = OBJC_TYPE_SEL_PRIM_TYPE;
  objc_type_list_add_set(&types, type);

  /* parameters */
  for (i = 0; i < nargs; i++) {
    type = objc_make_type();
    scan_node(pnodes[i], type, is_global);
    objc_type_list_add_set(&types, type);
  }
  return objc_make_type_encoding(types);
}

static objc_type_list_t
scan_node(objc_node_t node, objc_type_t type, BOOL is_global)
{
  int i;
  objc_class_t cls;
  objc_ivar_list_t ivars;
  objc_type_t subtype;
  objc_type_list_t subtypes = NULL;

  if (node == NULL)
    return NULL;

  switch (node->type) {
  case OBJC_SPCR_QUAL_LIST_NODE:
  case OBJC_STRUCT_DCLR_LIST_NODE:
    for (i = 0; i < node->a.l.nsubs; i++)
      scan_node(node->a.l.subs[i], type, is_global);
    break;

  case OBJC_IVAR_DECL_NODE:
    /* scan pointer first */
    scan_node(node->a.ivar_decl.dclr, type, is_global);
    scan_node(node->a.ivar_decl.spcr_qual_list, type, is_global);
    break;

  case OBJC_TYPE_NAME_NODE:
    /* scan pointer first */
    scan_node(node->a.type_name.abs_dclr, type, is_global);
    scan_node(node->a.type_name.spcr_qual_list, type, is_global);
    break;

  case OBJC_TYPEDEF_NAME_NODE:
    if (STREQ(node->a.sval, OBJC_TYPE_ID_NAME)) {
      type->code_type = OBJC_TYPE_PRIM_CODE_TYPE;
      type->body.prim = OBJC_TYPE_ID_PRIM_TYPE;
    } else if (STREQ(node->a.sval, OBJC_TYPE_SEL_NAME)) {
      type->code_type = OBJC_TYPE_PRIM_CODE_TYPE;
      type->body.prim = OBJC_TYPE_SEL_PRIM_TYPE;
    } else if (STREQ(node->a.sval, OBJC_TYPE_CLASS_NAME)) {
      type->code_type = OBJC_TYPE_PRIM_CODE_TYPE;
      if (is_global)
        type->body.prim = OBJC_TYPE_ID_PRIM_TYPE;
      else
        type->body.prim = OBJC_TYPE_CLASS_PRIM_TYPE;
    } else {
      /* IMPLEMENTME */
    }
    break;

  case OBJC_INIT_DCLR_NODE:
    scan_node(node->a.init_dclr.dclr, type, is_global);
    break;

  case OBJC_DCLR_NODE:
    scan_node(node->a.dclr.base_dclr, type, is_global);
    break;

  case OBJC_BASE_DCLR_NODE:
    /* scan pointer first */
    scan_node(node->a.base_dclr.ptr, type, is_global);
    scan_node(node->a.base_dclr.dir_dclr, type, is_global);
    break;

  case OBJC_TYPE_QUAL_NODE:
    switch (node->a.word) {
    case CONST: case CONST2: case CONST3:
      type->quals |= OBJC_TYPE_CONST_QUAL;
      break;
    }
    break;

  case OBJC_TYPE_SPCR_NODE:
    type->code_type = OBJC_TYPE_PRIM_CODE_TYPE; 
    switch (node->a.word) {
    case CHAR:
      if (type->nptrs > 0)
        type->body.prim = OBJC_TYPE_STR_PRIM_TYPE;
      else
        type->body.prim = OBJC_TYPE_CHAR_PRIM_TYPE;
      break;

    case UNSIGNED:
      type->body.prim = OBJC_TYPE_UINT_PRIM_TYPE;
      break;

    case SHORT:
      if (type->body.prim == OBJC_TYPE_UINT_PRIM_TYPE)
        type->body.prim = OBJC_TYPE_USHORT_PRIM_TYPE;
      else
        type->body.prim = OBJC_TYPE_SHORT_PRIM_TYPE;
      break;

    case INT:
      if (type->body.prim == OBJC_TYPE_UINT_PRIM_TYPE)
        type->body.prim = OBJC_TYPE_UINT_PRIM_TYPE;
      else
        type->body.prim = OBJC_TYPE_INT_PRIM_TYPE;
      break;

    case LONG:
      if (type->body.prim == OBJC_TYPE_UINT_PRIM_TYPE)
        type->body.prim = OBJC_TYPE_ULONG_PRIM_TYPE;
      else
        type->body.prim = OBJC_TYPE_LONG_PRIM_TYPE;
      break;

    case FLOAT:     type->body.prim = OBJC_TYPE_FLOAT_PRIM_TYPE;    break;
    case DOUBLE:    type->body.prim = OBJC_TYPE_DOUBLE_PRIM_TYPE;   break;
    case VOID:      type->body.prim = OBJC_TYPE_VOID_PRIM_TYPE;     break;
    case INSTANCE:  type->body.prim = OBJC_TYPE_ID_PRIM_TYPE;       break;
    case SELF:      type->body.prim = OBJC_TYPE_ID_PRIM_TYPE;       break;
    }
    break;

  case OBJC_KW_DCLR_NODE:
    if (node->a.kw_dclr.mtype)
      scan_node(node->a.kw_dclr.mtype, type, is_global);
    else {
      type->code_type = OBJC_TYPE_PRIM_CODE_TYPE; 
      type->body.prim = OBJC_TYPE_ID_PRIM_TYPE;
    }
    break;

  case OBJC_ABS_DCLR_NODE:
    scan_node(node->a.abs_dclr.ptr, type, is_global);
    scan_node(node->a.abs_dclr.dir_abs_dclr, type, is_global);
    break;

  case OBJC_PTR_NODE:
    type->nptrs++;
    scan_node(node->a.ptr.ptr, type, is_global);
    break;

  case OBJC_CLS_NAME_NODE:
    if (type->nptrs > 0) {
      type->code_type = OBJC_TYPE_PRIM_CODE_TYPE;
      type->body.prim = OBJC_TYPE_ID_PRIM_TYPE;
      type->nptrs--;
    } else {
      type->code_type = OBJC_TYPE_STRUCT_CODE_TYPE;
      type->body.su.name = node->a.sval;
      cls = objc_get_class(node->a.sval);
      type->body.su.mems = NULL;
      ivars = cls->ivars;
      while (ivars) {
        subtype = objc_make_type();
        scan_node(ivars->car->node, subtype, is_global);
        objc_type_list_add_set(&type->body.su.mems, subtype);
        ivars = ivars->cdr;
      }
    }
    break;

  case OBJC_IDENT_NODE:
    type->code_type = OBJC_TYPE_PRIM_CODE_TYPE;
    type->body.prim = OBJC_TYPE_UNKNOWN_PRIM_TYPE;
    break;

  case OBJC_SU_SPCR_NODE:
    if (node->a.su_spcr.type)
      type->code_type = OBJC_TYPE_STRUCT_CODE_TYPE;
    else
      type->code_type = OBJC_TYPE_UNION_CODE_TYPE;
    type->body.su.name = node->a.su_spcr.name;
    type->body.su.mems = NULL;

    if (type->nptrs <= 1) {
      if (type->body.su.name != NULL) {
        type->body.su.mems = scan_node(objc_scope_get_struct(objc_root_scope, type->body.su.name)->
                             a.type.spcr->a.su_spcr.decl_list, NULL, is_global);
      } else
        type->body.su.mems = scan_node(node->a.su_spcr.decl_list, NULL, is_global);
    }
    break;

  case OBJC_STRUCT_DECL_LIST_NODE:
    for (i = 0; i < node->a.l.nsubs; i++) {
      subtype = objc_make_type();
      scan_node(node->a.l.subs[i], subtype, is_global);
      objc_type_list_add_set(&subtypes, subtype);
    }
    break;

  case OBJC_STRUCT_DECL_NODE:
    scan_node(node->a.struct_decl.dclr_list, type, is_global);
    scan_node(node->a.struct_decl.spcr_qual_list, type, is_global);
    break;

  case OBJC_STRUCT_DCLR_NODE:
    /* bitfield */
    /*
    if (node->a.struct_dclr.expr)
      type->body.nbits = node->a.struct_dclr.expr;
    else
      scan_node(node->a.struct_dclr.dclr, type, is_global);
    */
    if (!node->a.struct_dclr.expr)
      scan_node(node->a.struct_dclr.dclr, type, is_global);
    break;

  default:
    objc_print_error("cannot convert node %s to type encoding",
                     objc_node_name(node));
  }

  return subtypes;
}

char *
objc_generate_type_name(objc_node_t node, BOOL is_global)
{
  objc_str_stream_t st;

  st = objc_make_str_stream();
  scan_node_type_name(st, node, is_global);

  return objc_str_stream_value(st);
}

static void
scan_node_type_name(objc_str_stream_t st, objc_node_t node, BOOL is_global)
{
  int i;

  if (node == NULL)
    return;

  switch (node->type) {
  case OBJC_SPCR_QUAL_LIST_NODE:
    for (i = 0; i < node->a.l.nsubs; i++) {
      scan_node_type_name(st, node->a.l.subs[i], is_global);
      objc_str_stream_write(st, " ");
    }
    break;

  case OBJC_ABS_DCLR_NODE:
    scan_node_type_name(st, node->a.abs_dclr.ptr, is_global);
    scan_node_type_name(st, node->a.abs_dclr.dir_abs_dclr, is_global);
    break;

  case OBJC_BASE_DCLR_NODE:
    scan_node_type_name(st, node->a.base_dclr.ptr, is_global);
    scan_node_type_name(st, node->a.base_dclr.dir_dclr, is_global);
    break;

  case OBJC_CLS_NAME_NODE:
    if (is_global)
      /* scamping */
      objc_str_stream_write(st, "struct objc_object ");
    else
      objc_str_stream_write(st, node->a.sval);
    break;

  case OBJC_DCLR_NODE:
    scan_node_type_name(st, node->a.dclr.base_dclr, is_global);
    break;

  case OBJC_IDENT_NODE:
  case OBJC_MTH_UNARY_SEL_NODE:
    /* ignore */
    break;

  case OBJC_IVAR_DECL_NODE:
    scan_node_type_name(st, node->a.ivar_decl.spcr_qual_list, is_global);
    scan_node_type_name(st, node->a.ivar_decl.dclr, is_global);
    break;

  case OBJC_KW_DCLR_NODE:
    if (node->a.kw_dclr.mtype)
      scan_node_type_name(st, node->a.kw_dclr.mtype, is_global);
    else
      objc_str_stream_write(st, "id");
    break;

  case OBJC_KW_SEL_NODE:
    for (i = 0; i < node->a.l.nsubs; i++) {
      scan_node_type_name(st, node->a.l.subs[i], is_global);
      if (i+1 < node->a.l.nsubs)
        objc_str_stream_write(st, ", ");
    }
    break;

  case OBJC_MTH_KW_SEL_NODE:
    scan_node_type_name(st, node->a.mth_kw_sel.sel, is_global);
    if (node->a.mth_kw_sel.vargs)
      objc_str_stream_write(st, ", ...");
    break;

  case OBJC_PTR_NODE:
    objc_str_stream_write(st, "*");
    scan_node_type_name(st, node->a.ptr.type_qual_list, is_global);
    scan_node_type_name(st, node->a.ptr.ptr, is_global);
    break;

  case OBJC_TYPE_NAME_NODE:
    scan_node_type_name(st, node->a.type_name.spcr_qual_list, is_global);
    scan_node_type_name(st, node->a.type_name.abs_dclr, is_global);
    break;

  case OBJC_TYPEDEF_NAME_NODE:
    objc_str_stream_write(st, node->a.sval);
    break;

  case OBJC_TYPE_QUAL_NODE:
    switch (node->a.word) {
    case CONST:     objc_str_stream_write(st, "const");         break;
    case CONST2:    objc_str_stream_write(st, "__const");       break;
    case CONST3:    objc_str_stream_write(st, "__const__");     break;
    case VOLATILE:  objc_str_stream_write(st, "volatile");      break;
    case VOLATILE2: objc_str_stream_write(st, "__volatile");    break;
    case VOLATILE3: objc_str_stream_write(st, "__volatile__");  break;
    }
    break;

  case OBJC_TYPE_SPCR_NODE:
    switch (node->a.word) {
    case CHAR:      objc_str_stream_write(st, "char");       break;
    case SHORT:     objc_str_stream_write(st, "short");      break;
    case INT:       objc_str_stream_write(st, "int");        break;
    case LONG:      objc_str_stream_write(st, "long");       break;
    case FLOAT:     objc_str_stream_write(st, "float");      break;
    case DOUBLE:    objc_str_stream_write(st, "double");     break;
    case VOID:      objc_str_stream_write(st, "void");       break;
    case UNSIGNED:  objc_str_stream_write(st, "unsigned");   break;
    case SIGNED:    objc_str_stream_write(st, "signed");     break;
    case SIGNED2:   objc_str_stream_write(st, "__signed");   break;
    case SIGNED3:   objc_str_stream_write(st, "__signed__"); break;

    case INSTANCE:
    case SELF:
      objc_str_stream_write(st, "id");
      break;
    }
    break;

  case OBJC_STRUCT_DCLR_NODE:
    scan_node_type_name(st, node->a.struct_dclr.dclr, is_global);
    break;

  case OBJC_SU_SPCR_NODE:
    objc_str_stream_write(st, node->a.su_spcr.type ? "struct " : "union ");
    objc_str_stream_write(st, node->a.su_spcr.name);
    break;

  default:
    objc_print_error(__FILE__ ": scan_node_type_name(): "
                    "%s: not supported", objc_node_name(node));
  }
}

